package cn.lingyangwl.agile.auth.controller;

import cn.lingyangwl.agile.auth.assembly.ThirdAuthAssembly;
import cn.lingyangwl.agile.auth.model.ThirdBindContent;
import cn.lingyangwl.agile.auth.model.rqrs.third.*;
import cn.lingyangwl.agile.auth.oauth2.model.OAuth2TokenResp;
import cn.lingyangwl.agile.auth.oauth2.model.RegisteredClient;
import cn.lingyangwl.agile.auth.service.OAuth2ClientService;
import cn.lingyangwl.agile.auth.service.OAuth2TokenService;
import cn.lingyangwl.agile.auth.service.QrcodeLoginService;
import cn.lingyangwl.agile.auth.service.ThirdClientService;
import cn.lingyangwl.agile.auth.third.BaseThirdAuth;
import cn.lingyangwl.agile.common.security.annotation.InnerAuth;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.enums.AuthErrorEnum;
import cn.lingyangwl.agile.model.module.auth.GrantTypeEnum;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.system.SystemApi;
import cn.lingyangwl.agile.tenant.core.utils.TenantUtils;
import cn.lingyangwl.framework.core.response.Resp;
import cn.lingyangwl.framework.tool.core.exception.Assert;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.view.RedirectView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;

/**
 * 这里用于工程第三方的授权, 即使是多租户的, 一般也不会让对外提供的租户配置这种三分登录渠道
 * , 所以这里统一由平台租户管理
 *
 * @author shenguangyang
 */
@Slf4j
@Api(tags = "第三方授权")
@RestController
@RequestMapping("/thirdAuth")
public class ThirdAuthController {
    @Resource
    private OAuth2ClientService clientService;;
    @Resource
    private QrcodeLoginService qrcodeLoginService;
    @Resource
    private ThirdAuthAssembly thirdAssembly;
    @Resource
    private OAuth2TokenService tokenService;
    @Resource
    private ThirdClientService thirdClientService;
    @Resource
    private SystemApi systemApi;

    /**
     * 获取第三方登录的地址
     * @param tenantId 一定要填写主租户id
     */
    @GetMapping("/authorize/{tenantId}/{clientId}/{source}")
    public Object authorize(
            @PathVariable("source") String source,
            @PathVariable("clientId") String clientId,
            @PathVariable("tenantId") String tenantId,
            AuthUrlGetReq req) {
        log.info("第三方登录进入render：" + source);
        // 校验客户端是否支持当前身份源登录
        RegisteredClient client = clientService.validOAuthClient(clientId, null, source, null, null);
        req.setClient(client);
        try {
            return TenantUtils.execute(DataIsolateLevel.MAIN, tenantId, () -> {
                BaseThirdAuth socialAuth = BaseThirdAuth.getInstance(GrantTypeEnum.of(source));
                ThirdAuthResp authorize = socialAuth.authorize(req);
                log.info("第三方登录认证地址：" + authorize.getPageUrl());
                if (Boolean.TRUE.equals(authorize.getIsRedirect())) {
                    return new RedirectView(authorize.getPageUrl());
                }
                return Resp.ok(authorize);
            });
        } finally {
            TenantContext.clear();
        }
    }

    /**
     * 跳转到登录页面, 由于在开发阶段, 微信回调本地vite启动的前端页面, 需要进行内网穿透,
     * 但是在vite开发环境时候, 会占用大量的流量(比较费钱), 所以开发阶段需要让微信直接回调后端接口, 后端接口需要进行内网穿透,
     * 然后重定向到前端第三方登录页面, 再去做逻辑登录处理
     */
    @GetMapping("/goLogin")
    public Object goLogin(HttpServletRequest request) {
        StringBuffer urlParams = new StringBuffer();
        for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            urlParams.append(entry.getKey()).append("=").append(entry.getValue()[0]).append("&");
        }
        log.info("第三方登录进入 go login：{}", urlParams);
//        return new RedirectView("http://127.0.0.1:3100/thirdAuth/login?" + urlParams );
        return new RedirectView("http://127.0.0.1:8080/pages/users/wechat_login/index?" + urlParams );
    }

    /**
     * oauth平台中配置的授权回调地址，在创建github授权应用时的回调地址应为：
     * <a href="http://127.0.0.1:8443/auth/third/login/test/github">地址</a>
     *
     * 前端在调用 {@link #authorize(String, String, Long, AuthUrlGetReq)} 后可以把 grantType 存入到本地缓存,
     * 在第三方授权成功并重定向到项目的前端页面时候, 再从缓存中取出 grantType 参数并调用login接口, 完成后续的登录逻辑
     * @param tenantId 一定要填写主租户id
     */
    @GetMapping("/login/{tenantId}/{clientId}/{grantType}")
    public Object login(@PathVariable("grantType") String grantType,
                        @PathVariable("clientId") String clientId,
                        @PathVariable("tenantId") String tenantId,
                        ThirdLoginReq req) {
        // 校验客户端是否支持当前身份源登录
        RegisteredClient client = clientService.validOAuthClient(clientId, null, grantType, null, null);
        req.setClient(client);

        try {
            return TenantUtils.execute(DataIsolateLevel.MAIN, tenantId, () -> {
                BaseThirdAuth thirdAuth = BaseThirdAuth.getInstance(GrantTypeEnum.of(grantType));
                ThirdAuthUser authUser = thirdAuth.login(req);
                // 授权成功之后, 可能配置了回调到前端页面, 然后前端页面再调用登录接口, 这样前端可能不需要后端进行重定向, 所以
                // 这里优先使用前端传递的值
                authUser.setIsRedirect(Objects.nonNull(req.getIsRedirect()) ? req.getIsRedirect() : authUser.getIsRedirect());
                if (Boolean.TRUE.equals(authUser.getIsRedirect())) {
                    return new RedirectView(authUser.getRedirectUrl());
                }
                return Resp.ok(authUser);
            });
        } finally {
            TenantContext.clear();
        }
    }

    /**
     * 绑定账号
     * @param token 来自 {@link ThirdAuthController#login(String, String, Long, ThirdLoginReq)} 返回的token
     */
    @PostMapping("/bind")
    public Resp<OAuth2TokenResp> bind(@Validated @RequestBody ThirdFirstBindReq req,
                                      @RequestHeader(HttpHeaders.AUTHORIZATION) String token) {
        OAuth2TokenResp tokenInfo = checkAccessToken(token);

        return TenantUtils.execute(DataIsolateLevel.MAIN, tokenInfo.getLoginTenantId(), () -> {
            ThirdBindContent content = thirdAssembly.toThirdBindContent(req);

            // step 查找客户端
            RegisteredClient client = clientService.validOAuthClient(tokenInfo.getClientId(), null, null, null, null);
            Assert.notNull(client, new BizException("授权客户端不存在, 请重新发起登录"));

            content.setTokenInfo(tokenInfo);
            content.setClient(client);

            BaseThirdAuth thirdAuth = BaseThirdAuth.getInstance(GrantTypeEnum.of(tokenInfo.getGrantType()));

            return Resp.ok(thirdAuth.firstBind(content));
        });
    }

    /**
     * 获取登录二维码信息
     */
    @GetMapping("/getLoginQrcode")
    public Resp<LoginQrcodeResp> getLoginQrcode(@Validated LoginQrcodeGetReq req) {
        return Resp.ok(qrcodeLoginService.getLoginQrcode(req));
    }

    /**
     * 获取登录二维码扫码后的token结果
     */
    @GetMapping("/getQrcodeToken")
    public Resp<LoginQrcodeTokenResp> getQrcodeToken(@RequestParam("qrcodeId") String qrcodeId) {
        LoginQrcodeTokenResp qrcodeToken = qrcodeLoginService.getQrcodeToken(qrcodeId);
        if (Objects.isNull(qrcodeToken)) {
            return Resp.fail(AuthErrorEnum.QR_CODE_EXPIRED);
        }
        return Resp.ok(qrcodeToken);
    }

    /**
     * 用户进行了扫码操作
     */
    @PostMapping("/userScanQrcode")
    @InnerAuth
    public Resp<?> userScanQrcode(@RequestBody @Validated LoginQrcodeInfo req) {
        qrcodeLoginService.userScanQrcode(req);
        return Resp.ok();
    }


    /**
     * 获取用户信息
     */
    @GetMapping("/getUserInfo")
    public Resp<ThirdUserInfoResp> getUserInfo(@Validated ThirdUserGetReq req) {
        OAuth2TokenResp tokenInfo = checkAccessToken(req.getAccessToken());

        return TenantUtils.execute(DataIsolateLevel.MAIN, TenantCons.SYSTEM_TENANT_ID, () -> {
            // step 查找客户端
            RegisteredClient client = clientService.validOAuthClient(tokenInfo.getClientId(), null, null, null, null);
            Assert.notNull(client, new BizException("授权客户端不存在, 请重新发起登录"));

            BaseThirdAuth thirdAuth = BaseThirdAuth.getInstance(GrantTypeEnum.of(tokenInfo.getGrantType()));

            req.setClient(client);;

            return Resp.ok(thirdAuth.getUserInfo(req));
        });
    }

    public OAuth2TokenResp checkAccessToken(String accessToken) {
        OAuth2TokenResp tokenInfo = tokenService.checkAccessToken(accessToken);
        if (StringUtils.isAnyEmpty(tokenInfo.getOpenId(), tokenInfo.getGrantType())) {
            throw new BizException("不合法的三方授权参数, 请重新发起登录");
        }
        return tokenInfo;
    }


    /**
     * 参考 <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html">文档</a>
     */
    @PostMapping("/createWeixinJsapiSignature")
    @ApiOperation(value = "创建微信 JS SDK 初始化所需的签名")
    public Resp<WxJsapiSignatureResp> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
        WxJsapiSignature wxMpJsapiSignature = thirdClientService.createWxMpJsapiSignature(url);
        return Resp.ok(thirdAssembly.toWxJsapiSignatureResp(wxMpJsapiSignature));
    }
}
